﻿using System;
using System.Text;

namespace PacketRush.Core
{
    /// <summary>
    /// Reads bytes from a <see cref="PacketRush.Core.Packet"/>.
    /// </summary>
    /// <remarks>
    /// <para>
    /// <c>PacketReader</c> can be used to read bytes from a <see cref="PacketRush.Core.Packet"/>.
    /// </para>
    /// <para>
    /// When calling <c>ReadX()</c> methods on an instance of <c>PacketReader</c> it will read bytes from the current
    /// position and advance to the next position by the number of bytes that make up the data type that was read.
    /// </para>
    /// <para>
    /// To determine if bytes can be read from the current position use the <c>CanReadX()</c> methods before calling
    /// a corresponding <c>ReadX()</c> method.
    /// </para>
    /// <para>
    /// To create packets, see <see cref="PacketRush.Core.PacketWriter"/>.
    /// </para>
    /// </remarks>
    /// <example>
    /// The following example shows how to read values from a packet using <c>PacketReader</c>, note that the <c>CanReadX()</c> methods used
    /// below are purely to demonstrate that PacketReader has this capability but in practice it may not be appropriate
    /// to check before every read.
    /// <code source="..\PacketRush.Core\DocExamples\PacketReader.cs" region="Example01"></code>
    /// </example>
    /// <seealso cref="PacketRush.Core.Packet"/>
    /// <seealso cref="PacketRush.Core.PacketWriter"/>
    public class PacketReader
    {
        private byte[] _RawPacket;
        /// <summary>
        /// Represents the <see cref="PacketRush.Core.Packet"/> to read in raw bytes.
        /// </summary>
        public byte[] RawPacket
        {
            get
            {
                return _RawPacket;
            }
            set
            {
                _RawPacket = value;
                Reset();
            }
        }
        private int _Pos = Packet.HeaderLengthInBytes;

        /// <summary>
        /// Creates an instance of <c>PacketReader</c>, after creating an instance you must
        /// set <see cref="PacketRush.Core.PacketReader.RawPacket"/> with the packet to read.
        /// </summary>
        public PacketReader() { }

        /// <summary>
        /// Create an instance of the <c>PacketReader</c> with the packet to read from.
        /// </summary>
        /// <param name="packet">The packet to read from.</param>
        public PacketReader(Packet packet)
        {
            RawPacket = packet;
        }

        /// <summary>
        /// Create an instance of the <c>PacketReader</c> with the raw packet to read from.
        /// </summary>
        /// <param name="rawPacket">The packet to read from.</param>
        public PacketReader(byte[] rawPacket)
        {
            RawPacket = rawPacket;
        }

        /// <summary>
        /// Reset the <c>PacketReader</c> to the first byte of the packet following the packet header.
        /// </summary>
        private void Reset()
        {
            _Pos = Packet.HeaderLengthInBytes;
        }

        /// <summary>
        /// Reads a byte from the current position and advances to the next position.
        /// </summary>
        /// <returns>A byte</returns>
        /// <exception cref="System.ArgumentOutOfRangeException">Thrown when the PacketReader attempts to read beyond the size of the packet.</exception>
        /// <exception cref="System.NullReferenceException">Thrown if the data being read is null.</exception>
        public byte ReadByte()
        {
            return RawPacket[_Pos++];
        }

        /// <summary>
        /// Determine if the given number of bytes can be read at the current position.
        /// </summary>
        /// <param name="numBytes">The number of bytes to read.</param>
        /// <returns>True if bytes can be read.</returns>
        /// <exception cref="System.NullReferenceException">Thrown if the data being read is null.</exception>
        public bool CanReadBytes(int numBytes)
        {
            if (_Pos + numBytes <= RawPacket.Length) return true;

            return false;
        }

        /// <summary>
        /// Determine if a byte can be read from the packet at the current position.
        /// </summary>
        /// <returns>True if a byte can be read.</returns>
        /// <exception cref="System.NullReferenceException">Thrown if the data being read is null.</exception>
        public bool CanReadByte()
        {
            return CanReadBytes(1);
        }

        /// <summary>
        /// Reads an unsigned 16-bit integer from the current position and advances to the next position.
        /// </summary>
        /// <returns>An unsigned 16-bit integer.</returns>
        /// <exception cref="System.ArgumentOutOfRangeException">Thrown when the PacketReader attempts to read beyond the size of the packet.</exception>
        /// <exception cref="System.NullReferenceException">Thrown if the data being read is null.</exception>
        public ushort ReadUInt16()
        {
            ushort val = BitConverter.ToUInt16(RawPacket, _Pos);
            _Pos += 2;
            return val;
        }

        /// <summary>
        /// Determine if an unsigned 16-bit integer can be read at the current position.
        /// </summary>
        /// <returns>True if an unsigned 16-bit integer can be read.</returns>
        /// <exception cref="System.NullReferenceException">Thrown if the data being read is null.</exception>
        public bool CanReadUInt16()
        {
            return CanReadBytes(2);
        }

        /// <summary>
        /// Reads a signed 16-bit integer from the current position and advances to the next position.
        /// </summary>
        /// <returns>A signed 16-bit integer.</returns>
        /// <exception cref="System.ArgumentOutOfRangeException">Thrown when the PacketReader attempts to read beyond the size of the packet.</exception>
        /// <exception cref="System.NullReferenceException">Thrown if the data being read is null.</exception>
        public short ReadInt16()
        {
            short val = BitConverter.ToInt16(RawPacket, _Pos);
            _Pos += 2;
            return val;
        }

        /// <summary>
        /// Determine if a signed 16-bit integer can be read at the current position.
        /// </summary>
        /// <returns>True if a signed 16-bit integer can be read.</returns>
        /// <exception cref="System.NullReferenceException">Thrown if the data being read is null.</exception>
        public bool CanReadInt16()
        {
            return CanReadBytes(2);
        }

        /// <summary>
        /// Reads an unsigned 32-bit integer from the current position and advances to the next position.
        /// </summary>
        /// <returns>An unsigned 32-bit integer.</returns>
        /// <exception cref="System.ArgumentOutOfRangeException">Thrown when the PacketReader attempts to read beyond the size of the packet.</exception>
        /// <exception cref="System.NullReferenceException">Thrown if the data being read is null.</exception>
        public uint ReadUInt32()
        {
            uint val = BitConverter.ToUInt32(RawPacket, _Pos);
            _Pos += 4;
            return val;
        }

        /// <summary>
        /// Determine if an unsigned 32-bit integer can be read at the current position.
        /// </summary>
        /// <returns>True if an unsigned 32-bit integer can be read.</returns>
        /// <exception cref="System.NullReferenceException">Thrown if the data being read is null.</exception>
        public bool CanReadUInt32()
        {
            return CanReadBytes(4);
        }

        /// <summary>
        /// Reads a signed 32-bit integer from the current position and advances to the next position.
        /// </summary>
        /// <returns>A signed 32-bit integer.</returns>
        /// <exception cref="System.ArgumentOutOfRangeException">Thrown when the PacketReader attempts to read beyond the size of the packet.</exception>
        /// <exception cref="System.NullReferenceException">Thrown if the data being read is null.</exception>
        public int ReadInt32()
        {
            int val = BitConverter.ToInt32(RawPacket, _Pos);
            _Pos += 4;
            return val;
        }

        /// <summary>
        /// Determine if a signed 32-bit integer can be read at the current position.
        /// </summary>
        /// <returns>True if a signed 32-bit integer can be read.</returns>
        /// <exception cref="System.NullReferenceException">Thrown if the data being read is null.</exception>
        public bool CanReadInt32()
        {
            return CanReadBytes(4);
        }

        /// <summary>
        /// Reads an unsigned 64-bit integer from the current position and advances to the next position.
        /// </summary>
        /// <returns>An unsigned 64-bit integer.</returns>
        /// <exception cref="System.ArgumentOutOfRangeException">Thrown when the PacketReader attempts to read beyond the size of the packet.</exception>
        /// <exception cref="System.NullReferenceException">Thrown if the data being read is null.</exception>
        public ulong ReadUInt64()
        {
            ulong val = BitConverter.ToUInt64(RawPacket, _Pos);
            _Pos += 8;
            return val;
        }

        /// <summary>
        /// Determine if an unsigned 64-bit integer can be read at the current position.
        /// </summary>
        /// <returns>True if an unsigned 64-bit integer can be read.</returns>
        /// <exception cref="System.NullReferenceException">Thrown if the data being read is null.</exception>
        public bool CanReadUInt64()
        {
            return CanReadBytes(8);
        }

        /// <summary>
        /// Reads a signed 64-bit integer from the current position and advances to the next position.
        /// </summary>
        /// <returns>A signed 64-bit integer.</returns>
        /// <exception cref="System.ArgumentOutOfRangeException">Thrown when the PacketReader attempts to read beyond the size of the packet.</exception>
        /// <exception cref="System.NullReferenceException">Thrown if the data being read is null.</exception>
        public long ReadInt64()
        {
            long val = BitConverter.ToInt64(RawPacket, _Pos);
            _Pos += 8;
            return val;
        }

        /// <summary>
        /// Determine if a signed 64-bit integer can be read at the current position.
        /// </summary>
        /// <returns>True if a signed 64-bit integer can be read.</returns>
        /// <exception cref="System.NullReferenceException">Thrown if the data being read is null.</exception>
        public bool CanReadInt64()
        {
            return CanReadBytes(8);
        }

        /// <summary>
        /// Reads a float from the current position and advances to the next position.
        /// </summary>
        /// <returns>A float.</returns>
        /// <exception cref="System.ArgumentOutOfRangeException">Thrown when the PacketReader attempts to read beyond the size of the packet.</exception>
        /// <exception cref="System.NullReferenceException">Thrown if the data being read is null.</exception>
        public float ReadSingle()
        {
            float val = BitConverter.ToSingle(RawPacket, _Pos);
            _Pos += 4;
            return val;
        }

        /// <summary>
        /// Determine if a float can be read at the current position.
        /// </summary>
        /// <returns>True if a float can be read.</returns>
        /// <exception cref="System.NullReferenceException">Thrown if the data being read is null.</exception>
        public bool CanReadSingle()
        {
            return CanReadBytes(4);
        }

        /// <summary>
        /// Reads a double from the current position and advances to the next position.
        /// </summary>
        /// <returns>A double.</returns>
        /// <exception cref="System.ArgumentOutOfRangeException">Thrown when the PacketReader attempts to read beyond the size of the packet.</exception>
        /// <exception cref="System.NullReferenceException">Thrown if the data being read is null.</exception>
        public double ReadDouble()
        {
            double val = BitConverter.ToDouble(RawPacket, _Pos);
            _Pos += 8;
            return val;
        }

        /// <summary>
        /// Determine if a double can be read at the current position.
        /// </summary>
        /// <returns>True if a double can be read.</returns>
        /// <exception cref="System.NullReferenceException">Thrown if the data being read is null.</exception>
        public bool CanReadDouble()
        {
            return CanReadBytes(8);
        }

        /// <summary>
        /// Reads a char from the current position and advances to the next position.
        /// </summary>
        /// <returns>A char.</returns>
        /// <exception cref="System.ArgumentOutOfRangeException">Thrown when the PacketReader attempts to read beyond the size of the packet.</exception>
        /// <exception cref="System.NullReferenceException">Thrown if the data being read is null.</exception>
        public char ReadChar()
        {
            char val = BitConverter.ToChar(RawPacket, _Pos);
            _Pos += 2;
            return val;
        }

        /// <summary>
        /// Determine if a char can be read at the current position.
        /// </summary>
        /// <returns>True if a char can be read.</returns>
        /// <exception cref="System.NullReferenceException">Thrown if the data being read is null.</exception>
        public bool CanReadChar()
        {
            return CanReadBytes(2);
        }

        /// <summary>
        /// Reads a bool from the current position and advances to the next position.
        /// </summary>
        /// <returns>A bool.</returns>
        /// <exception cref="System.ArgumentOutOfRangeException">Thrown when the PacketReader attempts to read beyond the size of the packet.</exception>
        /// <exception cref="System.NullReferenceException">Thrown if the data being read is null.</exception>
        public bool ReadBoolean()
        {
            bool val = BitConverter.ToBoolean(RawPacket, _Pos);
            _Pos += 1;
            return val;
        }

        /// <summary>
        /// Determine if a bool can be read at the current position.
        /// </summary>
        /// <returns>True if a bool can be read.</returns>
        /// <exception cref="System.NullReferenceException">Thrown if the data being read is null.</exception>
        public bool CanReadBoolean()
        {
            return CanReadBytes(1);
        }

        /// <summary>
        /// Reads bytes from the current position and advances to the next position.
        /// </summary>
        /// <returns>A bool.</returns>
        /// <exception cref="System.ArgumentOutOfRangeException">Thrown when the PacketReader attempts to read beyond the size of the packet.</exception>
        /// <exception cref="System.NullReferenceException">Thrown if the data being read is null.</exception>
        public byte[] ReadBytes(int numBytes)
        {
            byte[] bytes = new byte[numBytes];
            Buffer.BlockCopy(RawPacket, _Pos, bytes, 0, numBytes);
            _Pos += numBytes;
            return bytes;
        }

        /// <summary>
        /// Reads a string from the current position and advances to the next position.
        /// </summary>
        /// <returns>A string.</returns>
        /// <exception cref="System.ArgumentOutOfRangeException">Thrown when the PacketReader attempts to read beyond the size of the packet.</exception>
        /// <exception cref="System.NullReferenceException">Thrown if the data being read is null.</exception>
        public string ReadString(Encoding encoding)
        {
            int len = ReadInt32();
            string s = encoding.GetString(RawPacket, _Pos, len);
            _Pos += len;
            return s;
        }

        /// <summary>
        /// Determine if a string can be read at the current position.
        /// </summary>
        /// <returns>True if a string can be read.</returns>
        /// <exception cref="System.NullReferenceException">Thrown if the data being read is null.</exception>
        public bool CanReadString()
        {
            return CanReadBytes(6);
        }
    }
}
